//
// Database.cpp
// libprotea
// Tools Library for the Protea Project / Database Handler
// Copyright (C) 2003, 2004, 2005 eXegeSys, Inc.
// Copyright (C) 2008 Bruce A. James
//
// This program is part of the Protea Project.
//
// The Protea Project is free software; you can redistribute it and/or modify it 
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation, either version 3 of the License, or (at your 
// option) any later version.
//
// The Protea Project is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
// for more details.
// 
// You should have received a copy of the GNU General Public License along 
// with this program.  If not, see <http://www.gnu.org/licenses/>.
// 
// Please send correspondence to:
// 
// theproteaproject@gmail.com
//

// Database.cpp

#include "../defines.h"
#include "Database.h"
#include "prepSQL.h"
#include "DBInterface.h"
#include "../Security/base64.h"
#include "../Security/encrypt.h"
#include <stdarg.h>

#include "../defns.h"
using namespace ProteaApp1;

// XCDBErrorMap Class
//////////////////////////////////////////////////////////////////////////////
XCDBErrorMap::XCDBErrorMap( long xcerrormsg, long dberrormsg )
{
      xcerror = xcerrormsg;
      dberror = dberrormsg;
      next = NULL;
};

// XCDBDriverDescr Class
//////////////////////////////////////////////////////////////////////////////
XCDBDriverDescr::XCDBDriverDescr()
{
      type = db_none;
      size = 0;
      version = 0;
      can_two_phase_commit = false;
      can_update_defaults = false;
      can_exclusive_access = false;
      varchar_max_size = 0;
      blob_max_size = 0;
      errormap = NULL;
};

XCDBDriverDescr::~XCDBDriverDescr()
{
	XCDBErrorMap* temp = errormap;
	while ( temp ) {
		errormap = temp;
		temp = temp->next;
		delete errormap;
	};
};

long XCDBDriverDescr::GetError( long dberr )
{
	XCDBErrorMap* temp = errormap;
	while ( temp ) {
		if ( temp->dberror == dberr )
		return temp->xcerror;
		temp = temp->next;
	};
	return 0;
};

void XCDBDriverDescr::AddError( long xcerr, long dberr )
{
	XCDBErrorMap* temp = new XCDBErrorMap( xcerr, dberr );
	temp->next = errormap;
	errormap = temp;
};



// XCCompare Class
//////////////////////////////////////////////////////////////////////////////

XCCompare::XCCompare()
{
	logical = logic_and;
	comparison = cmp_equals;
	format = -1;
	preparen = 0;
	postparen = 0;
	field = NULL;
	field2 = NULL;
};

XCCompare::XCCompare( const XCCompare& new_compare )
{
	logical = new_compare.logical;
	comparison = new_compare.comparison;
	format = new_compare.format;
	preparen = new_compare.preparen;
	postparen = new_compare.postparen;
	field = new_compare.field;
	field2 = new_compare.field2;
};


// XCOrder
//////////////////////////////////////////////////////////////////////////////

XCOrder::XCOrder()
{
	field = NULL;
	direction = order_asc;
	format = NULL;
};

// XCJoin
//////////////////////////////////////////////////////////////////////////////

XCJoin::XCJoin()
{
	type = join_inner;
	buffer = NULL;
	predicate = NULL;
}


// XCTxn
//////////////////////////////////////////////////////////////////////////////

typedef XCDBTransaction* XCDBTransactionPtr;

// Constructors & Destructors


XCTxn::XCTxn( XCTxn* p, XCDBHandler* h )
{
	INFUNC( XCTxn::XCTxn, h->appi )

	txncount = 0;
	parent = p;
	handler = h;
	txnarray = new XCDBTransactionPtr[handler->dbarraycount];
	txnarraycount = 0;
	isolation = repeatable_read;

	// If we have a parent, artificially increment the counter on the parent...
	if (parent)
		parent->IncrementCounter( 1 );

#ifdef TXNVALID
	if ( !parent )
	{
		SetValidTxn( true );
	}

	if ( !GetValidTxn() )
	{
		RAISETOOLSERROR( ERR_BAD_TXN );
	}
#endif

	OUTFUNC()
};

XCTxn::~XCTxn()
{
	INFUNC( XCTxn::~XCTxn, handler->appi )

	for (int i=0; i<txnarraycount; i++)
		delete txnarray[i];
	delete [] txnarray;

	OUTFUNC()
};


// Public functions
void XCTxn::EndTxn( bool commit )
{
	INFUNC( XCTxn::EndTxn, handler->appi )
	if (commit)
	{
		// handle the commit reference count
		IncrementCounter( -1 );
	}
	else
	{
		// handle the rollback reference count
		IncrementCounter( 0 );
	}

	delete this;
	OUTFUNC()
};



XCDBStatement* XCTxn::ExecuteSQL( XCSQLGen* query, exec_parm parm )
{
	XCDBStatement* retval = NULL;
	int i;
	INFUNC( XCTxn::ExecuteSQL, handler->appi )

	long dbnumber = query->GetDatabaseNbr();

	// Locate DB interface
	XCDBInterface* dbi = handler->GetInterface( dbnumber );
	if (dbi)
	{
		XCDBTransaction* temp = UseDatabase( dbnumber, true, isolation );
		try {
			retval = temp->CreateStmt2( query );
			retval->MakeStmt();
			if( handler->appi->options.debug_level_cutoff[5] >= TLV_LOW && (handler->appi->options.debug_type_filter & TRC_DATABASE) == TRC_DATABASE ) {
				char sqlstmt[ MAX_SQL_LEN ];			//^ this 5 is for TRC_DATABASE
				handler->appi->ToolsTrace( TRC_DATABASE, TLV_LOW, "ExecuteSQL : \n\tSTMT: %s\n\tDB #: %d", retval->GetSQLString( sqlstmt ), dbnumber );
			}
			retval->Execute();
		} catch( ... ) {
			if (retval)
				retval->Release();
			throw;
		};
	}

	if (parm & SQL_NO_RESULT) {
		if (retval) {
			retval->Release();
		}
		retval = NULL;
	};

	OUTFUNCRET( retval )
};

XCDBStatement* XCTxn::ExecuteSQL( XCPrepSQLGen* query, exec_parm parm )
{
	XCDBStatement* retval = NULL;
	int i;
	INFUNC( XCTxn::ExecuteSQL, handler->appi )

	long dbnumber = query->GetDatabaseNbr();

	// Locate DB interface
	XCDBInterface* dbi = handler->GetInterface( dbnumber );
	if (dbi)
	{
		XCDBTransaction* temp = UseDatabase( dbnumber, true, isolation );
		try {
			retval = temp->CreateStmt2( query );
			retval->MakeStmt();							
			if( handler->appi->options.debug_level_cutoff[5] >= TLV_HIGH && (handler->appi->options.debug_type_filter & TRC_DATABASE) == TRC_DATABASE ) {
				char sqlstmt[ MAX_SQL_LEN ];			//^ this 5 is for TRC_DATABASE
				handler->appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "ExecuteSQL : \n\tSTMT: %s\n\tDB #: %d", retval->GetSQLString( sqlstmt ), dbnumber );
			}
			retval->Execute();
		} catch( ... ) {
			if (retval)
				retval->Release();
			throw;
		};
	}

	if (parm & SQL_NO_RESULT) {
		if (retval) {
			retval->Release();
		}
		retval = NULL;
	};

	OUTFUNCRET( retval )
};

void XCTxn::LockTables( int count, XCLockInfo* lock, bool conditionally ) {
	int i;

	INFUNC( XCTxn::LockTable, handler->appi );

	// Issue the lock statement for each buffer
	XCLockInfo* current = lock;
	for (i = 0; i<count; i++) {
		long dbnumber = current->buffer->GetDatabaseInfo()->number;
		XCDBInterface* dbi = handler->GetInterface( dbnumber );
		if (dbi) {
			XCDBTransaction* temp = UseDatabase( dbnumber, false, isolation );
			temp->LockTable( current->buffer, current->field );
		};

		current++;
	};

	// Start a transaction in each database
	current = lock;
	for (i = 0; i<count; i++) {
		long dbnumber = current->buffer->GetDatabaseInfo()->number;

		XCDBInterface* dbi = handler->GetInterface( dbnumber );
		if (dbi) {
			XCDBTransaction* temp = UseDatabase( dbnumber, true, isolation );
			temp->LockTable( current->buffer, current->field );
		};

		current++;
	};

	
	OUTFUNC();
};


// Private functions
/////////////////////////////////////////////////



// Reference counting for commit/rollback of transactions
void XCTxn::IncrementCounter( int count )
{
	INFUNC( XCTxn::IncrementCounter, handler->appi )
	if (parent)
	{
		handler->appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "-->Push to parent" );
		parent->IncrementCounter( count );
		EXITFUNC();
	}

	handler->appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Count = %d", count );

	// check for rollback
	if (count == 0) {
		handler->appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Start rollback: txns=%d", txnarraycount );
		for (int i = 0; i < txnarraycount; i++)
		{
			txnarray[i]->Rollback();
#ifdef TXNVALID
			SetValidTxn( false );
#endif
		}
	} else {

#ifdef TXNVALID
		if ( !GetValidTxn() )
			RAISETOOLSERROR( ERR_BAD_TXN );
#endif

		txncount += count;

		// check for commit
		if (txncount == 0)
		{
			for (int j = 0; j < txnarraycount; j++)
			{
				txnarray[j]->Commit();
#ifdef TXNVALID
				SetValidTxn( false );
#endif
			}	
		}
	}
	OUTFUNC()
};


// Maintain list of used databases, for commit/rollback of txns across databases
XCDBTransaction* XCTxn::UseDatabase( long dbnum, bool start_txn, isolevel level )
{
	INFUNC( XCTxn::UseBuffer, handler->appi )

	XCDBTransaction* retval = NULL;
	if (parent) {
		handler->appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "-->Defer to parent" );
		retval = parent->UseDatabase( dbnum, start_txn );
	} else {
		// check txnarray for current txn in the database
		bool found = false;

		for (int i = 0; i < txnarraycount; i++)
		{
			if (dbnum == txnarray[i]->GetDatabaseNumber() )
			{
				found = true;
				retval = txnarray[i];
				i = txnarraycount;
			}
		}

		// not in array, open new transaction
		if (!found)
		{
			assert( handler );
			XCDBConnection* db_conn = handler->GetConnection( dbnum );
			retval = db_conn->CreateTxn();
			txnarray[txnarraycount++] = retval;
		}


		// See if we actually need to start a transaction on the database
		if (start_txn && !retval->started) {
			retval->Begin( level );
			IncrementCounter( 1 );
			retval->started = true;
		};
	};

	OUTFUNCRET( retval )
};


#ifdef TXNVALID

bool XCTxn::GetValidTxn()
{
	if (parent)
	{
		return parent->GetValidTxn();
	}
	else
	{
		return validtxn;
	}

};


void XCTxn::SetValidTxn( bool valid )
{
	if (parent)
	{
		parent->SetValidTxn( valid );
	}
	else
	{
		validtxn = valid;
	}
};

#endif

// XCDBHandler
//////////////////////////////////////////////////////////////////////////////

// Constructors & Destructors
XCDBHandler::XCDBHandler()
{
	INFUNC( XCDBHandler::XCDBHandler, NULL )
	dbarray = NULL;
	dbarraycount = 0;
	appi = NULL;
	OUTFUNC()
}


XCDBHandler::~XCDBHandler()
{
	INFUNC( XCDBHandler::~XCDBHandler, appi )

//	appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "In Delete db array, count = %ld", (long)dbarraycount );

	// delete dbarray
	if (dbarray) {
		XCDBHandlerConnection* connection = dbarray;
		for (long i = 0; i < dbarraycount; i++) {
			if (connection->dbi) {
//				appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Disconnect %d (%x)", i, dbarray[i].dbi );
//				cout << "Disconnect and kill: " << i << endl;
				if (connection->connection)
					connection->connection->Disconnect();

				delete connection->dbi;
			};
//			connection->dl.Close();

//			DeleteDBInterface( dbarray[i].dl );

			// Move on to next item
			dbarray++;
		}

		// Why does uncommenting this cause seg. faults?
		//delete [] dbarray;
	}

//	appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Delete db array - done" );

	OUTFUNC()
};


int XCDBHandler::Initialize( XCAppInterface* a )
{
	INFUNC( XCDBHandler::Initialize, a )
	appi = a;

	assert( appi );

	// Get all Database info...
	buffer_header* header = appi->appdic_header;
	assert( header );

	if (header->databases.count) {
		buffer_db_defn* db_defn = (buffer_db_defn*)appi->FixOffset( header->databases.offset );

		dbarraycount = header->databases.count;
		dbarray = new XCDBHandlerConnection[dbarraycount];

		for (long i = 0; i < dbarraycount; i++) {
//			cout << "New database: " << db_defn->name << "  " << db_defn->number << endl;
//			cout << "  " << db_defn->user << "  " << db_defn->pass << endl;

			dbarray[i].defns = db_defn;
			try {
				dbarray[i].dbi = GetDBInterface( (db_type)db_defn->type, &(dbarray[i].dl), &dbarray[i], appi );
				dbarray[i].connection = Connect( dbarray[i].defns->number );
			} catch( ... ) {
				cout << "Unable to initialize database interface..." << endl;
				dbarray[i].dbi = NULL;
				throw;
			};

			dbarray[i].app = a;

//			cout << "Init db: " << i << endl;
			appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "New dbarray element %d, %s, %x", i, dbarray[i].defns->name, dbarray[i].dbi );
			db_defn++;
		};
	} else {
		dbarraycount = 0;
		dbarray = NULL;
	};

	OUTFUNCRET( 1 )
};
 
XCTxn* XCDBHandler::CreateTxn( XCTxn* parent )
{
	XCTxn* retval = new XCTxn( parent, this );
	return retval;
};



XCDBHandlerConnection* XCDBHandler::GetHandlerConnection( long dbnumber ) {
	INFUNC( XCDBHandler::GetInterface, appi )

	int i;
	for (i = 0; i < dbarraycount; i++) {
		if (dbarray[i].defns->number == dbnumber) break;
	}
	if (i == dbarraycount)
		RAISETOOLSERROR( ERR_BAD_DB_INT );

	OUTFUNCRET( &dbarray[i] );
};



XCDBInterface* XCDBHandler::GetInterface( long dbnumber ) {
	return GetHandlerConnection( dbnumber )->dbi;
};

XCDBConnection* XCDBHandler::GetConnection( long dbnumber ) {
	INFUNC( XCDBHandler::GetInterface, appi )

	XCDBHandlerConnection* handler = GetHandlerConnection( dbnumber );

	// We found our database interface, now see if we have a connection
	if (!handler->connection) {
		// If not, issue a connection
		handler->connection = Connect( dbnumber );
	};

	OUTFUNCRET( handler->connection );
};

XCDBConnection* XCDBHandler::Connect( long dbnbr )
{
	XCDBConnection* retval = NULL;
	INFUNC( XCDBHandler::Connect, appi )
	
	char* user = NULL;
	char* pass = NULL;

	XCDBHandlerConnection* handle_conn = GetHandlerConnection( dbnbr );

	user = handle_conn->defns->user;
	pass = handle_conn->defns->pass;
	if (!user || !pass) 
		RAISETOOLSERROR( ERR_BAD_DB_LOGIN );

	// Unencrypt the password before sending it to the database interface
	XCBase64 base64;
	char result[ 256 ];
	int resultlen = base64.Decode( pass, (unsigned char*)result, strlen(pass) );
	int decryptlen;
	char* password = XCDecrypt( result, resultlen, decryptlen  );

	// Execute the connection
	retval = handle_conn->dbi->Connect( user, password );

	// Clear the password from memory
	memset( password, 0, decryptlen );
	delete [] password;

	appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Connect: %d", dbnbr );

	OUTFUNCRET( retval )
};

void XCDBHandler::Refresh()
{
	INFUNC( XCDBHandler::Refresh, appi )
	for (int i = 0; i < dbarraycount; i++)
	{
		appi->ToolsTrace( TRC_DATABASE, TLV_HIGH, "Refresh" );
		if ( !dbarray[i].connection->Refresh() ) 
		{
			dbarray[i].connection->Disconnect();
			delete dbarray[i].connection;
			dbarray[i].connection = NULL;

			dbarray[i].connection = Connect( dbarray[i].defns->number );
		}
	}
	OUTFUNC()
};



/*****************
	XCSQLGen class
*/

XCSQLGen::XCSQLGen()
{
	app = NULL;
	buffer = NULL;
	fields = NULL;
	compares = NULL;
	orders = NULL;
	joins = NULL;	
	limit = 0;

	Clear();
}


XCSQLGen::XCSQLGen( XCAppInterface *new_app ) 
{
	app = new_app;
	buffer = NULL;
	fields = NULL;
	compares = NULL;
	orders = NULL;
	joins = NULL;
	limit = 0;

	Clear();
}

XCSQLGen::XCSQLGen( const XCSQLGen &new_SQLGen )
{
	int i;
	app = new_SQLGen.app;
	type = new_SQLGen.type;
	buffer = new_SQLGen.buffer;
	group = new_SQLGen.group;
	distinct = new_SQLGen.distinct;
	limit = new_SQLGen.limit;

	// copy fields
	FieldCount = new_SQLGen.FieldCount;
	fields = new XCField*[FieldCount];
	for(i=0; i<FieldCount; i++)
		fields[i] = new_SQLGen.fields[i];

	// copy compares
	CompareCount = new_SQLGen.CompareCount;
	compares = new XCCompare*[CompareCount];
	for(i=0; i<CompareCount; i++)  
		compares[i] = new XCCompare( *new_SQLGen.compares[i] );		// copy whole thing, not just pointer
	
	// copy orders
	OrderCount = new_SQLGen.OrderCount;
	orders = new XCOrder*[OrderCount];
	for( i=0; i<OrderCount; i++)
		orders[i] = new_SQLGen.orders[i];

	// copy joins
	JoinCount = new_SQLGen.JoinCount;
	joins = new XCJoin*[JoinCount];
	for(i=0; i<JoinCount; i++) 
		joins[i] = new_SQLGen.joins[i];

}

XCSQLGen::XCSQLGen( sql_stmt_type sqltype, XCBuffer* buf, XCAppInterface* new_app )
{
	app = new_app;
	limit = 0;
	MakeFull( sqltype, buf );
}

XCSQLGen::~XCSQLGen() 
{
	Clear();
}

void XCSQLGen::Clear() 
{
	int i;
	if (fields)
		delete [] fields;
	if (compares) {
		for( i=0; i<CompareCount; i++ )
			delete compares[i];
		delete [] compares;
	}
	if (orders)
		delete [] orders;
	if (joins)
		delete [] joins;
	FieldCount = 0;
	fields = NULL;
	CompareCount = 0;
	compares = NULL;
	OrderCount = 0;
	orders = NULL;
	JoinCount = 0;
	joins = NULL;

	buffer = NULL;
	group = NULL;
	distinct = false;
	type = SQL_STMT_NONE;
	limit = 0;
}

void XCSQLGen::MakeFull( enum sql_stmt_type sqltype, XCBuffer* buf, XCBuffer* changes ) 
{
	INFUNC( XCSQLGen::MakeFull, app );
	int i;
	bool KeyFound = false;
	int lnum = 0;
	XCField** lfields = new XCField*[buf->GetFieldCount()+1];	// + 1 for null termination

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();		// clear out any old info

	if( sqltype == SQL_STMT_UPDATE )
		MakeUPDATE( buf, changes );		// UPDATE needs to deal with changes
	else {
		type = sqltype;					// other all do the same thing (since all fields are included)
		buffer = buf;
	}
	
	buffer_db_tables* table;
	if (changes)
		table = changes->GetDatasetDescr();
	else
		table = buf->GetDatasetDescr();

	if (!table)
		RAISETOOLSERROR_1PARAM( ERR_DB_NONEXISTENT_TABLE, buf->GetFormatName() );

	// Add a WHERE clause from key values in buf (except for an INSERT)
	if( type != SQL_STMT_INSERT ) {
		// If we are supplied two buffers, use the original buffer to set the where clause
		XCBuffer* key_buffer = buf;
		if (type == SQL_STMT_UPDATE && changes)
			key_buffer = changes;

		// First look for a primary key
		buffer_db_relate* rel = (buffer_db_relate*)app->FixOffset( table->relate_offset );
		for(i=0; i<table->relate_count; i++) {
			if  (rel->type == dbr_primary_key) {
				long field_num = ((buffer_field*)app->FixOffset(rel->field_offset))->number;
				lfields[lnum++] = key_buffer->GetField( field_num );
				KeyFound = true;
			};
			rel++;
		};

		// Then try to base it on the first unique key
		if (!KeyFound) {
			buffer_db_relate* rel = (buffer_db_relate*)app->FixOffset( table->relate_offset );
			for (i = 0; i < table->relate_count; i++) {
				if (rel->type == dbr_unique_key) {
					long field_num = ((buffer_field*)app->FixOffset(rel->field_offset))->number;
					lfields[lnum++] = key_buffer->GetField( field_num );
					KeyFound = true;
				};
				rel++;
			};
		};

		// only add if a key was found ...
		if (KeyFound)
		{
			lfields[lnum] = NULL;

			// build XCCompare array
			XCCompare* dycomp = new XCCompare[lnum];
			long fmt = key_buffer->GetFormatNumber();
			for (i = 0; i < lnum; i++)
			{
				dycomp[i].format = fmt;
			}

			// Actually add the clauses
			AddWHERE( lfields, dycomp );
			
			delete [] dycomp;
		} else
			RAISETOOLSERROR_1PARAM( ERR_REQUIRE_PRIMARY_KEY, buf->GetFormatName() );
	}

	delete [] lfields;

	OUTFUNC();
}

void XCSQLGen::SetSelectLimit( long numrows )
{
	INFUNC( XCSQLGen::SetSelectLimit, app );
	limit = numrows;
	OUTFUNC();
};

void XCSQLGen::MakeSELECT( XCBuffer *buf, int fieldcount, ...  )
{
	INFUNC( XCSQLGen::MakeSELECT, app );
	XCField* fld = NULL;
	int i;

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();

	type = SQL_STMT_SELECT;
	buffer = buf;
	FieldCount = fieldcount;

	if( FieldCount ){		// see if we need to use our fieldlist and not the buffers
		fields = new XCField*[FieldCount];
		va_list parmlist;
		va_start( parmlist, fieldcount );
		for( i=0; i<FieldCount; i++ ) {
			fld = (XCField*) va_arg( parmlist, void* );
			fields[i] = fld;
		}
	} 

	OUTFUNC();
}

void XCSQLGen::MakeSELECTByNum( XCBuffer *buf, int fieldcount, ...  )
{
	INFUNC( XCSQLGen::MakeSELECTByNum, app );
	long FieldNum;
	int i;

	Clear();

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	type = SQL_STMT_SELECT;
	buffer = buf;
	FieldCount = fieldcount;

	if( FieldCount ){		// see if we need to use our fieldlist and not the buffers
		fields = new XCField*[FieldCount];
		va_list parmlist;
		va_start( parmlist, fieldcount );
		for( i=0; i<FieldCount; i++ ) {
			FieldNum = va_arg( parmlist, long );
			fields[i] = buffer->GetField( FieldNum );
		}
	} 

	OUTFUNC();
}



void XCSQLGen::MakeSELECTExchange( XCBuffer* buf, XCBuffer* exchange ) {
	INFUNC( XCSQLGen::MakeSELECTExchange, app );
	long FieldNum;
	int i;

	Clear();

	// Ensure that we have been supplied buffer pointers
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );
	if (!exchange)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 2 );

	type = SQL_STMT_SELECT;
	buffer = buf;
	FieldCount = 0;

	// Allocate space for enough fields are in the buffer
	fields = new XCField*[ buffer->GetFieldCount() ];

	// Find all matching fields
	for (int i=0; i<buffer->GetFieldCount(); i++) {
		XCField* fld = buffer->GetFieldByIndex( i );
		if (exchange->GetField( fld->GetFieldNumber() )) {
			fields[ FieldCount ] = fld;
			FieldCount++;
		};
	};

	OUTFUNC();
};



void XCSQLGen::MakeSELECTDISTINCT( XCBuffer *buf, int fieldcount, ...  )
{
	INFUNC( XCSQLGen::MakeSELECT, app );
	XCField* fld = NULL;
	long FieldNum;
	int i;

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	type = SQL_STMT_SELECT;
	buffer = buf;
	distinct = true;
	FieldCount = fieldcount;
	if( FieldCount ) {
		fields = new XCField*[FieldCount];
		va_list parmlist;
		va_start( parmlist, fieldcount );
		for( i=0; i<FieldCount; i++ ) {
			FieldNum = va_arg( parmlist, long );
			fields[i] = buffer->GetField( FieldNum );
		}
		va_end( parmlist );
	}

	OUTFUNC();
}

void XCSQLGen::AddDISTINCT()
{
	INFUNC( XCSQLGen::AddDISTINCT, app );
	distinct = true;
	OUTFUNC();
}

void XCSQLGen::MakeINSERT( XCBuffer *buf )
{
	INFUNC( XCSQLGen::MakeINSERT, app );

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();
	type = SQL_STMT_INSERT;
	buffer = buf;
	OUTFUNC();
}

void XCSQLGen::MakeUPDATE( XCBuffer* buf, XCBuffer* changes )
{
	INFUNC( XCSQLGen::MakeUPDATE, app );
	int i, fcount;
	int *tempfields;
	XCField* fld;

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();

	type = SQL_STMT_UPDATE;
	buffer = buf;

	if( changes ) {
		// Buffers MUST be the same type
		if( changes->GetFormatNumber() != buf->GetFormatNumber() )
			RAISETOOLSERROR( 1 );

		// first just count the fields that are changed and save their index
		buf->MarkChanges( changes );
		FieldCount = 0;
		fcount = buf->GetFieldCount();
		tempfields = new int[fcount];	// can't have more than fcount changes
		for( i=0; i<fcount; i++ ) {		
			fld = buf->GetFieldByIndex(i);
			if( fld->IsChanged() )
				tempfields[FieldCount++] = i;
		}

		// If no changes have been made, trigger an error message
		if (!FieldCount) {
			delete [] tempfields;
			RAISETOOLSERROR( ERR_NO_DATA_CHANGE );
		};

		// now make the fields array and save the pointers to our changed fields
		fields = new XCField*[FieldCount];
		for( i=0; i<FieldCount; i++ )
			fields[i] = buf->GetFieldByIndex( tempfields[i] );

		delete [] tempfields;
	}

	OUTFUNC();
}

void XCSQLGen::MakeDELETE( XCBuffer* buf )
{
	INFUNC( XCSQLGen::MakeDELETE, app );

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();
	type = SQL_STMT_DELETE;
	buffer = buf;
	OUTFUNC();
}

void XCSQLGen::MakeROWLOCK( XCBuffer* buf, int count, ... ) 
{
	INFUNC( XCSQLGen::MakeROWLOCK, app );
	va_list parms;
	int i;
	long FieldNum;
	XCField* fld;
	int fcount = buf->GetFieldCount();

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	/////////////////////////////////////////////////////////////////////////////////////
	// "UPDATE "<table>" SET "<field1>"="<field1>" WHERE "<field2>"="<field2.value>" ...
	/////////////////////////////////////////////////////////////////////////////////////

	Clear();

	if( !count )
		RAISETOOLSERROR( ERR_BAD_SQL_STMT );	// close enough

	type = SQL_STMT_UPDATE;
	buffer = buf;
	
	va_start(parms, count);
	FieldNum = va_arg( parms, long );
	fld = buf->GetField( FieldNum );
	fields = new XCField*[1];	// just one field in the "SET" part
	fields[0] = fld;
	if( count > 1 ) {
		compares = new XCCompare*[count-1];		// the rest are compares
		for( i=0; i<count-1; i++ ) {
			XCCompare *comp = new XCCompare;
			FieldNum = va_arg( parms, long );
			comp->field = buf->GetField( FieldNum );
		}
	}
	va_end( parms );

	OUTFUNC();
}

void XCSQLGen::MakeROWLOCK( XCBuffer* buf, long* fieldlist )
{
	INFUNC( XCSQLGen::MakeROWLOCK, app );
	int i;
	int count;
	long FieldNum;
	XCField* fld;
	int fcount = buf->GetFieldCount();

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	/////////////////////////////////////////////////////////////////////////////////////
	// "UPDATE "<table>" SET "<field1>"="<field1>" WHERE "<field2>"="<field2.value>" ...
	/////////////////////////////////////////////////////////////////////////////////////

	Clear();

	if( !fieldlist )
		RAISETOOLSERROR( ERR_BAD_SQL_STMT );	// close enough

	type = SQL_STMT_UPDATE;
	buffer = buf;

	FieldNum = fieldlist[ 0 ];
	fld = buf->GetField( FieldNum );
	fields = new XCField*[1];	// just one field in the "SET" part
	fields[0] = fld;
	
	count = 0;
	i = 0;
	while ( fieldlist[ i ] )
	{
		count++;
		i++;
	};

	if( count > 1 ) {
		compares = new XCCompare*[count-1];		// the rest are compares
		for( i=0; i<count-1; i++ ) {
			XCCompare *comp = new XCCompare;
			FieldNum = fieldlist[ i ];
			comp->field = buf->GetField( FieldNum );
		}
	}

	OUTFUNC();
};

XCBuffer* XCSQLGen::MakeCOUNT( XCBuffer* buf, XCField* fld, bool distnct )
{
	INFUNC( XCSQLGen::MakeCOUNT, app );

	// Ensure that we have been supplied a buffer pointer
	if (!buf)
		RAISETOOLSERROR_1PARAM( ERR_INVALID_PARAMETER, 1 );

	Clear();
	type = SQL_STMT_COUNT;
	distinct = distnct;

	if( fld ) {
		fields = new XCField*[1];		//just one field to count
		fields[0] = fld;
		FieldCount = 1;
	}

	// now we make a buffer from scratch

	buffer_format* temp_fmt = new buffer_format;
	buffer_ds* temp_ds = new buffer_ds[1];
	buffer_field* temp_fld = new buffer_field;

	memset( temp_fmt, 0, sizeof( buffer_format ) );
	temp_fmt->number = 0;
	strcpy( temp_fmt->name, buf->GetFormatName() );		// this is so we know what table to count
	temp_fmt->type = buf->mydesc->type;
	temp_fmt->length = sizeof( long );
	temp_fmt->ds_count = 1;
	temp_fmt->ds_offset = (long)((char*)temp_ds - app->APPDIC_DATA);
	temp_fmt->default_offset = (long)( temp_fld->defaultval - app->APPDIC_DATA);
	temp_fmt->dd_offset = buf->mydesc->dd_offset;
	temp_fmt->reserved1 = 0;
	temp_fmt->reserved2 = 0;
	temp_fmt->child_format = 0;

	memset( temp_ds, 0, sizeof( temp_ds ) );
	temp_ds->field_offset = (long)((char*)temp_fld - app->APPDIC_DATA);
	temp_ds->data_offset = 0;
	temp_ds->min_length = 0;
	temp_ds->max_length = sizeof( long );
	temp_ds->alloc_size = sizeof( long );
	temp_ds->dec_places = 0;
	temp_ds->data_type = D_INTEGER;
	temp_ds->flags = 0;
	temp_ds->severity_code = 0;

	memset( temp_fld, 0, sizeof( temp_fld ) );
	temp_fld->number = 0;
	strcpy( temp_fld->name, "COUNT" );
	temp_fld->type = D_INTEGER;
	temp_fld->length = sizeof( long );
	temp_fld->decimal_places = 0;
	temp_fld->flags = 0;
	strcpy( temp_fld->defaultval, "0" );

	XCBuffer* retval = app->CreateDynamicBuffer( temp_fmt );
	buffer = retval;
	
	OUTFUNCRET( retval );
}

int XCSQLGen::EnlargeCompareList( int newsize )
{
	int i = 0;
	XCCompare** NewCompares;

	if( newsize < CompareCount )
		return -1;					//just don't deal with it

	// make a new array
	NewCompares = new XCCompare*[newsize];

	if( compares ) {							// if we have old compares
		for(i=0; i<CompareCount; i++) 			// copy them over to the new one
			NewCompares[i] = compares[i];

		delete [] compares;						// and ditch the old list
	}

	CompareCount = newsize;		//update the count
	compares = NewCompares;		//and the pointer

	return i;
}


void XCSQLGen::AddWHERE( XCField* field, XCCompare* compare )
{
	INFUNC( XCSQLGen::AddWHERE, app );
	int i;
	XCCompare *comp;

	if( compare ) 
		comp = new XCCompare( *compare );		//copy whole thing, not just pointer
	else
		comp = new XCCompare();					//just use the default
    
	comp->field = field;						//need to keep the field around

	i = EnlargeCompareList( CompareCount + 1 );	// enlarge our pointer array
	compares[i] = comp;							// and add the new compare

	OUTFUNC();
}

void XCSQLGen::AddWHERE( XCCompare* compare )
{
	INFUNC( XCSQLGen::AddWHERE, app );
	XCCompare *comp = new XCCompare( *compare );
	int i = EnlargeCompareList( CompareCount + 1 );
	compares[i] = comp;
	OUTFUNC();
}

void XCSQLGen::AddWHERE( long parmcount, ... )
{
	INFUNC( XCSQLGen::AddWHERE, app );
	XCField* fld = NULL;
	XCCompare *comp;
	va_list parmlist;
	int i;

	va_start( parmlist, parmcount );

	i = EnlargeCompareList( parmcount + CompareCount );

	for( ; i<CompareCount; i++ ) {
		fld = (XCField*) va_arg( parmlist, void* );
		comp = new XCCompare;		// make a new compare class
		comp->field = fld;			// set its field memeber
		compares[i] = comp;			// and save it in our new list
	}

	OUTFUNC();
}

void XCSQLGen::AddWHEREByNum( long parmcount, ... )
{
	INFUNC( XCSQLGen::AddWHEREByNum, app );
	long FieldNum;
	XCCompare *comp;
	va_list parmlist;
	int i;

	va_start( parmlist, parmcount );

	i = EnlargeCompareList( parmcount + CompareCount );

	for( ; i<CompareCount; i++ ) {
		FieldNum = va_arg( parmlist, long );
		comp = new XCCompare;		// make a new compare class
		comp->field = buffer->GetField( FieldNum );			// set its field memeber
		compares[i] = comp;			// and save it in our new list
	}

	OUTFUNC();
}


void XCSQLGen::AddWHERE( XCField** field, XCCompare* compare, long count )
{
	INFUNC( XCSQLGen::AddWHERE, app );
	XCField *fld;
	XCCompare *comp;
	int i, j;
	bool FieldFound;
	int FieldNum;

	if( count == -1 ) {		// if they didn't supply the count, count 'em up (assume null termination)
		count = 0;
		fld = field[count];
		while (fld) {
			count++;
			fld = field[count];
		}
	}

	i = EnlargeCompareList( count + CompareCount );

	//put the new fields in
	for( j=0; i<CompareCount; i++, j++) {
		fld = field[j];
		comp = new XCCompare( compare[j] );
//		printf("Setting where on %s.\n", fld->GetFieldName() );
		comp->field = fld;
		compares[i] = comp;
	}

	OUTFUNC()
}

int XCSQLGen::EnlargeOrderList( int newsize )
{
	int i = 0;
	XCOrder** NewOrders;

	if( newsize < OrderCount )
		return -1;					//just don't deal with it

	// make a new array
	NewOrders = new XCOrder*[newsize];

	if( orders ) {							// if we have old orders
		for(i=0; i<OrderCount; i++) 			// copy them over to the new one
			NewOrders[i] = orders[i];

		delete [] orders;						// and ditch the old list
	}

	OrderCount = newsize;		//update the count
	orders = NewOrders;		//and the pointer

	return i;
}


void XCSQLGen::AddORDERBY( long field, XCOrder* ord )
{
	INFUNC( XCSQLGen::AddORDERBY, app );
	int i;

	ord->field = buffer->GetField( field );
	i = EnlargeOrderList( OrderCount + 1 );
	orders[i] = ord;

	OUTFUNC()
}

void XCSQLGen::AddORDERBY( XCOrder* ord, int count )
{
	INFUNC( XCSQLGen::AddORDERBY, app );
	int i;

	i = EnlargeOrderList( OrderCount + count );
	for( ; i<OrderCount; i++ ) 
		orders[i] = ord++;

	OUTFUNC();
}

void XCSQLGen::AddORDERBY( long* field, XCOrder* ord, int count )
{
	INFUNC( XCSQLGen::AddORDERBY, app );
	int i, j;

	// see if they gave us a count, if not, count it for them
	if( count == -1 ) {
		count = 0;
		for( i=0; field[i]; i++ ) 
			count++;
	}

	// make room for the new orders
	i = EnlargeOrderList( OrderCount + count );

	// and put them in there
	for( j=0; i<OrderCount; i++, j++ ) {
		ord[j].field = buffer->GetField( field[j] );
		orders[i] = &ord[j];
	}

	OUTFUNC();
}

int XCSQLGen::EnlargeFieldList( int newsize )
{
	int i = 0;
	long BufFieldCount;
	XCField** NewFields;

	if( newsize < FieldCount )
		return -1;					//just don't deal with it

	// make a new array
	NewFields = new XCField*[newsize];

	if( fields ) {							// if we have old fields
		for(i=0; i<FieldCount; i++) 			// copy them over to the new one
			NewFields[i] = fields[i];

		delete [] fields;					// and ditch the old list
	} else if (buffer) {					// otherwise we need to add fields from our buffer
		BufFieldCount = buffer->GetFieldCount();	// because we want to include all the fields because FieldCount = 0
		if( newsize < BufFieldCount ) {		// need to make room for it
			delete [] NewFields;
			newsize = BufFieldCount;
			NewFields = new XCField*[BufFieldCount];
		}
		for( i=0; i<BufFieldCount; i++ )
			NewFields[i] = buffer->GetFieldByIndex( i );

	}
		
	FieldCount = newsize;		//update the count
	fields = NewFields;			//and the pointer

	return i;
}

int XCSQLGen::EnlargeJoinList( int newsize )
{
	int i = 0;
	XCJoin** NewJoins;

	if( newsize < JoinCount )
		return -1;					//just don't deal with it

	// make a new array
	NewJoins = new XCJoin*[newsize];

	if( joins ) {							// if we have old joins
		for(i=0; i<JoinCount; i++) 			// copy them over to the new one
			NewJoins[i] = joins[i];

		delete [] joins;					// and ditch the old list
	}

	JoinCount = newsize;		//update the count
	joins = NewJoins;			//and the pointer

	return i;
}

void XCSQLGen::AddJOIN( XCJoin* join, int fieldcount, ... )
{
	INFUNC( XCSQLGen::AddJOIN, app );
	va_list parmlist;
	int i, j;
	long BufFieldCount, JoinFieldCount;

	if( type != SQL_STMT_SELECT && type != SQL_STMT_COUNT )
		RAISETOOLSERROR( ERR_BAD_SQL_TYPE );

	i = EnlargeJoinList( JoinCount + 1 );		// first save the join pointer (and increment count)
	joins[i] = join;	

	va_start( parmlist, fieldcount );

	// NOTE: for simplicity, when joins are used we will ALWAYS use the field list.  If not every time you add
	// a join you'd have to go through them all and see if you need to generate the list or not.

	if (type == SQL_STMT_SELECT) {
		if( fieldcount )
			JoinFieldCount = fieldcount;
		else
			JoinFieldCount = join->buffer->GetFieldCount();

		if( FieldCount )
			BufFieldCount = FieldCount;
		else
			BufFieldCount = buffer->GetFieldCount();

		// EnlargeFieldList takes care of adding the fields from the buffer if necessary
		i = EnlargeFieldList( BufFieldCount + JoinFieldCount );											

		// now we know that i is the first open field pointer in the list and FieldCount is the total number in the list
		// so we can copy over the fields for the join
		for( j=0; i<FieldCount; i++, j++ ) {		
			if( fieldcount )	//use the fields from the list
				fields[i] = (XCField*)va_arg( parmlist, void* );
			else	// use the fields from the join's buffer
				fields[i] = join->buffer->GetFieldByIndex(j);
		}
	} else {
		JoinFieldCount = 0;
		BufFieldCount = 0;
	};

	OUTFUNC();
}

void XCSQLGen::AddJOINByNum( XCJoin* join, int fieldcount, ... )
{
	INFUNC( XCSQLGen::AddJOINByNum, app );
	va_list parmlist;
	int i, j;
	long BufFieldCount, JoinFieldCount;

	if( type != SQL_STMT_SELECT )
		RAISETOOLSERROR( ERR_BAD_SQL_TYPE );

	i = EnlargeJoinList( JoinCount + 1 );		// first save the join pointer (and increment count)
	joins[i] = join;	

	va_start( parmlist, fieldcount );

	// NOTE: for simplicity, when joins are used we will ALWAYS use the field list.  If not every time you add
	// a join you'd have to go through them all and see if you need to generate the list or not.

	if( fieldcount )
		JoinFieldCount = fieldcount;
	else
		JoinFieldCount = join->buffer->GetFieldCount();

	if( FieldCount )
		BufFieldCount = FieldCount;
	else
		BufFieldCount = buffer->GetFieldCount();

	// EnlargeFieldList takes care of adding the fields from the buffer if necessary
	i = EnlargeFieldList( BufFieldCount + JoinFieldCount );											

	// now we know that i is the first open field pointer in the list and FieldCount is the total number in the list
	// so we can copy over the fields for the join
	long FieldNum;
	for( j=0; i<FieldCount; i++, j++ ) {		
		if( fieldcount ) {	//use the fields from the list
			FieldNum = va_arg( parmlist, long );
			fields[i] = join->buffer->GetField( FieldNum );
		}
		else	// use the fields from the join's buffer
			fields[i] = join->buffer->GetFieldByIndex(j);
	}

	OUTFUNC();
}


long XCSQLGen::GetDatabaseNbr() {
	INFUNC( XCSQLGen::GetDatabaseNbr, app );

	buffer_db_defn* db = (buffer_db_defn*)app->FixOffset( buffer->GetDatasetDescr()->db_offset );
	if (!db)
		RAISETOOLSERROR( 1 );

	OUTFUNCRET( db->number );
};


